#import "Message.h"
#import "EnumTypes.h"

#import "Peers.h"

NSString *TypeConnectionPeer = @"P";
NSString *TypeConnectionTransfer = @"F";
NSString * TypeConnectionPierceFirewall = @"X";

@implementation Message
+ (id)message
{
	Message *msg = [[Message alloc] init];
	[msg autorelease];
	return msg;
}

+ (id)messageWithCode:(int)code {
	Message *msg = [[Message alloc] initWithCode:code];
	return [msg autorelease];
}

+ (id)messageWithData:(NSData *)newdata
{
	return [[Message alloc] initWithData:newdata];
}

+ (NSString *)stringFromData:(NSData *)data2 {
	char *stuff = (char *)malloc([data2 length]+1);
	[data2 getBytes:stuff];
	stuff[[data2 length]] = 0;
	NSString *s = [NSString stringWithUTF8String:stuff];
	if(s == nil) {
		s = [[[NSString alloc] initWithData:data2 encoding:[NSString defaultCStringEncoding]] autorelease];
		if(s == nil) {
			s = [[[NSString alloc] initWithBytes:[data2 bytes] length:[data2 length] encoding:NSNonLossyASCIIStringEncoding] autorelease];
			if(s == nil)
				s = [NSString stringWithCString:stuff length:[data2 length]];
		}
	}
	free(stuff);
	return s;
}	

- (id)init
{
	return [self initWithData:[NSMutableData dataWithCapacity:512]];
}

- (id)initWithCode:(int)code {
	self = [self init];
	if(self)
		[self putCode:code];
	return self;
}

- (id)initWithData:(NSData *)newdata
{
	if(self = [super init]) {
		data = [[NSMutableData alloc] initWithData:newdata];
		idx = 0;
		maxidx = 0;
		compressed = NO;
		completedDecompression = NO;
		age = [[NSDate alloc] init];
		peer = nil;
		delegate = nil;
	}
	return self;
}

- (void)dealloc
{
	[age release];
	age = nil;
	[data release];
	data = nil;
	[peer release];
	peer = nil;
	[delegate release];
	delegate = nil;
	[super dealloc];
}

- (void)rewind { maxidx = idx>maxidx?idx:maxidx; idx = 0; }
- (unsigned)index { return idx; }
- (unsigned)countBytesRemaining { return [data length] - idx; }
- (unsigned)countBytesUnread { return [data length] - (idx>maxidx?idx:maxidx); }
- (BOOL)canReadBytes:(unsigned)count { return count <= [self countBytesRemaining]; }
- (BOOL)canReadData { return [self canReadBytes:sizeof(unsigned int)]; }
- (NSDate *)age { return age; }

- (BOOL)decompress
{
	if(completedDecompression) 
		return YES;
	char *src;
	char *dst;
	unsigned long srcLen;
	unsigned long dstLen;
	int err;

	NSMutableData *new = [[NSMutableData alloc] init];

	src = (char *)[data bytes];

	src += idx;

	srcLen = [data length] - idx;

	//fejta: TODO: test out a smaller number than 25.
	dstLen = srcLen*5;
	int n=5;	//number of times to try.
	do {
		dstLen*=5;
		dst = (char *)malloc(dstLen);

		err = uncompress((Bytef *)dst, &dstLen, (Bytef *)src, srcLen);

		if (err == Z_OK)
		{
			[new appendBytes:(char *)[data bytes] length:idx];
			[new appendBytes:dst length:dstLen];
			[data release];
			data = [new retain];
		}
		else
		{
			if(err!=-3)
				NSLog(@"Error uncompressing message: %i", err);
			free(dst);
			continue;
		}

		free(dst);
		completedDecompression = YES;
		return completedDecompression;
	}
	while(n--);
	return NO;
}

- (void)compress
{
	compressed = YES;
	cmp_start = idx;
}

- (void)read:(void *)ptr len:(int)len
{
	if(idx+len > [data length]) {
		[NSException raise:@"BufferOverflowException" format:@"%d is longer than message length %d",idx+len,[data length]];
	}
	else {
		[data getBytes:ptr range:NSMakeRange(idx, len)];
		idx += len;
	}
}

#pragma mark accessor methods

- (NSData *)getData
{
	int len;
	void *ptr;
	
	len = [self getInt];
	
	if([self canReadBytes:len]==NO) {
		[NSException raise:@"BufferOverflowException" format:@"Buffer Overflow Avoided.  Cannot read data.  Need %d bytes.  %d remain.",len,[self countBytesRemaining]];
	}

	ptr = malloc(sizeof(void)*len);
	[self read:ptr len:len];
	NSData *data2 = [NSData dataWithBytes:ptr length:len];
	free(ptr);
	return data2;
}

- (NSString *)getString
{
	NSData *data2 = [self getData];
	return [Message stringFromData:data2];
}

- (char)getByte
{
	char val;
	[self read:&val len:sizeof(val)];
	return val;
}

- (short)getShort
{
	short val;
	[self read:&val len:sizeof(val)];
	val = NSSwapLittleShortToHost(val);
	return val;
}

- (int)getCode { return [self getInt]; }

- (int)getInt
{
	int val;
	[self read:&val len:sizeof(val)];
	val = NSSwapLittleIntToHost(val);
	return val;
}

- (long long int)getLongLong
{
    long long int val;
    [self read:&val len:sizeof(val)];
    val = NSSwapLittleLongLongToHost(val);
    return val;
}

- (void)put:(const void *)buf length:(int)len
{
	idx += len;
	[data appendBytes:buf length:len];
}

- (void)finish
{
	//call this function when you are done adding data to compress.
	if (compressed)
	{
		NSRange range = NSMakeRange(cmp_start, [data length] - cmp_start);
		char *ibuf = (char *)malloc(range.length);
		unsigned long len = range.length * 2;
		char *obuf = (char *)malloc(len);

		[data getBytes:ibuf range:range];

		if (compress((Bytef *)obuf, &len, (Bytef *)ibuf, range.length) != Z_OK)
		{
			NSLog(@"Error compressing.");
			free(ibuf);
			free(obuf);
			return;
		}
		[data replaceBytesInRange:range withBytes:obuf length:len];
		free(ibuf);
		free(obuf);
	}
}

#pragma mark settor methods.

- (void)putData:(NSData *)d
{
	[self putInt:[d length]];
	[self put:[d bytes] length:[d length]];
}

- (void)putString:(NSString *)str
{
	if([str canBeConvertedToEncoding:[NSString defaultCStringEncoding]]) {
		[self putInt:[str cStringLength]];
		[self put:[str cString] length:[str cStringLength]];
	}
	else {
		[self putUTFString:str];
	}
}

- (void)putUTFString:(NSString *)str {
	NSAssert(str,@"Cannot add a nil string to a message.");

	const char *utf = [str UTF8String];
	unsigned len = strlen(utf);
	[self putInt:len];
	[self put:utf length:len];
}

- (void)putCode:(int)v
{
	[self putInt:v];
}

- (void)putByte:(char)v
{
	[self put:&v length:sizeof(v)];
}

- (void)putShort:(short)v
{
	v = NSSwapHostShortToLittle(v);
	[self put:&v length:sizeof(v)];
}

- (void)putInt:(int)v
{
	v = NSSwapHostIntToLittle(v);
	[self put:&v length:sizeof(v)];
}

- (void)putLongLong:(long long int)v
{
	v = NSSwapLittleLongLongToHost(v);
	[self put:&v length:sizeof(v)];
}

- (NSData *)data
{
	return data;
}

- (Peer *)peer { return peer; }
- (void)setPeer:(Peer *)p { [p retain]; [peer release]; peer = p; }
- (id)delegate { return delegate; }
- (void)setDelegate:(id)dele { [dele retain]; [delegate release]; delegate = dele; }

@end